from utils.idandmeta import IdAndMeta
from utils.interval import Interval
from utils.prettyrepr import PrettyMeta, build_info, build_object_info, pretty_repr, pretty_str


class Item(IdAndMeta):
    """Simple object for holding the data relative to a single item."""
    __metaclass__ = PrettyMeta
    
    def __init__(self, id=None, release=None, due=None, weight=1.0, 
                 width=1.0, length=1.0, thickness=1.0, **meta):
        IdAndMeta.__init__(self, id, **meta)
        if release is not None and not isinstance(release, Interval):
            release = Interval(release, release)
        if due is not None and not isinstance(due, Interval):
            due = Interval(due, due)
        self.release   = release
        self.due       = due
        self.weight    = weight
        self.width     = width
        self.length    = length
        self.thickness = thickness
        self.preprocess()
        
    def deepcopy(self, target=None):
        if target is None:
            cls = type(self)
            target = cls.__new__(cls)
        IdAndMeta.deepcopy(self, target)
        target.release   = None if self.release is None else self.release.deepcopy()
        target.due       = None if self.due is None else self.due.deepcopy()
        target.weight    = self.weight
        target.width     = self.width
        target.length    = self.length
        target.thickness = self.thickness
        return target
        
    @property
    def dimensions(self):
        return self.length, self.width, self.thickness
        
    @property
    def min_stay(self):
        return max(0.0, self.due.start - self.release.end)
        
    @property
    def max_stay(self):
        return self.due.end - self.release.start
        
    @property
    def mid_stay(self):
        mid_release = (self.release.start + self.release.end) * 0.5
        mid_due = (self.due.start + self.due.end) * 0.5
        assert mid_due >= mid_release
        return mid_due - mid_release
        
    def __info__(self):
        return self.id
        
    def details(self):
        info = build_object_info(self, ["release", "due", "weight", "dimensions"], sep="\n  ")
        meta = build_info(self.meta, sep="\n    ")
        return pretty_repr(self, pretty_str(self, info + "\n  Meta={\n    " + meta + "\n  }"))
        
    def preprocess(self):
        """Check if the release and due intervals are feasible, and adjust the intervals to remove
        invalid periods of time (i.e., a due interval starting before the release interval, or a 
        release interval ending after the due interval). Note that this check only applies when 
        the item already has both intervals defined."""
        if self.release is None or self.due is None:
            return
        if self.release.start >= self.due.end:
            raise Exception("invalid intervals for %s" % self)
        if self.release.start > self.due.start:
            self.due = self.due.deepcopy()
            self.due.start = self.release.start
        if self.release.end > self.due.end:
            self.release = self.release.deepcopy()
            self.release.end = self.due.end
        assert self.release.start <= self.release.end <= self.due.end
        assert self.release.start <= self.due.start <= self.due.end
        
